Итоговое задание по визуализации¶

Содержание

Предподготовка
Набор данных для анализа
Описание датасета
Формальное описание заданий
Предобработка

Визуальный анализ

  1. Соотношение ушедших и лояльных клиентов
  2. Распределение баланса пользователей
  3. Распределение баланса клиента в разрезе признака оттока
  4. Распределение возраста в разрезе признака оттока
  5. Взаимосвязь кредитного рейтинга клиента и его предполагаемой зарплаты
  6. Соотношение ушедших клиентов по половому признаку
  7. Соотношение оттока клиентов от числа приобретённых у банка услуг
  8. Влияние наличия статуса активного клиента на отток клиентов
  9. Распределение ушедших клиентов по странам. Тепловая картограмма
  10. Работа с категорией кредитного рейтинга. Тепловая карта по оттоку

Приложение

Предподготовка и загрузка модулей¶


Для визуализаций используется библиотека plotly.express

Раскомментируйте команду в следующей ячейке для ее установки при необходимости

In [1]:
#!pip install "plotly==5.8.2"
In [2]:
import numpy as np
import pandas as pd

import plotly.express as px
#import plotly.graph_objects as go


import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

# %config IPCompleter.greedy=True
# %config InlineBackend.figure_format = 'retina'

# псевдо-объект для промежуточных, масок и прочего
class _WorkData: pass 
churn = _WorkData()

Данные об оттоке клиентов некоторого банка¶

In [3]:
# Исходный набор данных
churn.data = pd.read_csv('data/churn.zip')
churn.data.head()
Out[3]:
RowNumber CustomerId Surname CreditScore Geography Gender Age Tenure Balance NumOfProducts HasCrCard IsActiveMember EstimatedSalary Exited
0 1 15634602 Hargrave 619 France Female 42 2 0.00 1 1 1 101348.88 1
1 2 15647311 Hill 608 Spain Female 41 1 83807.86 1 0 1 112542.58 0
2 3 15619304 Onio 502 France Female 42 8 159660.80 3 1 0 113931.57 1
3 4 15701354 Boni 699 France Female 39 1 0.00 2 0 0 93826.63 0
4 5 15737888 Mitchell 850 Spain Female 43 2 125510.82 1 1 1 79084.10 0
In [4]:
churn.data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 14 columns):
 #   Column           Non-Null Count  Dtype  
---  ------           --------------  -----  
 0   RowNumber        10000 non-null  int64  
 1   CustomerId       10000 non-null  int64  
 2   Surname          10000 non-null  object 
 3   CreditScore      10000 non-null  int64  
 4   Geography        10000 non-null  object 
 5   Gender           10000 non-null  object 
 6   Age              10000 non-null  int64  
 7   Tenure           10000 non-null  int64  
 8   Balance          10000 non-null  float64
 9   NumOfProducts    10000 non-null  int64  
 10  HasCrCard        10000 non-null  int64  
 11  IsActiveMember   10000 non-null  int64  
 12  EstimatedSalary  10000 non-null  float64
 13  Exited           10000 non-null  int64  
dtypes: float64(2), int64(9), object(3)
memory usage: 1.1+ MB

Описание Датасета¶

Наименование столбца Описание
RowNumber Номер строки таблицы (это лишняя информация, поэтому можете сразу от неё избавиться)
CustomerId Идентификатор клиента
Surname Фамилия клиента
CreditScore Кредитный рейтинг клиента (чем он выше, тем больше клиент брал кредитов и возвращал их)
Geography Страна клиента (банк международный)
Gender Пол клиента
Age Возраст клиента
Tenure Сколько лет клиент пользуется услугами банка
Balance Баланс на счетах клиента в банке
NumOfProducts Количество услуг банка, которые приобрёл клиент
HasCrCard Есть ли у клиента кредитная карта (1 — да, 0 — нет)
IsActiveMember Есть ли у клиента статус активного клиента банка (1 — да, 0 — нет)
EstimatedSalary Предполагаемая заработная плата клиента
Exited Статус лояльности (1 — ушедший клиент, 0 — лояльный клиент)

К содержанию


Легенда¶

Итак, банк обращается к вам за помощью: он хочет разработать кампанию лояльности по удержанию клиентов, но для этого ему необходимо, чтобы вы выяснили основные причины оттока клиентов. Иными словами, нужно установить, чем ушедшие клиенты отличаются от лояльных и как между собой связаны различные признаки, определяющие клиентов.

Задания¶

  1. Каково соотношение ушедших и лояльных клиентов? Покажите это на графике и дайте комментарий по соотношению.

  2. Постройте график, показывающий распределение баланса пользователей, у которых на счету больше 2 500 долларов. Опишите распределение и сделайте выводы.

  3. Посмотрите на распределение баланса клиента в разрезе признака оттока. Как различаются суммы на накопительном счёте ушедших и лояльных клиентов? Подумайте и напишите, с чем это может быть связано, что может не устраивать ушедших клиентов в банке.

  4. Посмотрите на распределение возраста в разрезе признака оттока. В какой группе больше потенциальных выбросов? На какую возрастную категорию клиентов стоит обратить внимание банку?

  5. Постройте график, который показывает взаимосвязь кредитного рейтинга клиента и его предполагаемой зарплаты. Добавьте расцветку по признаку оттока клиентов. Какова взаимосвязь между признаками? Если не видите явной взаимосвязи, укажите это.

  6. Кто чаще уходит, мужчины или женщины? Постройте график, который иллюстрирует это.

    Процент ушедших клиентов в каждой группе можно рассчитать как среднее по столбцу Exited (так как 1 — это ушедшие клиенты, а 0 — лояльные, среднее арифметическое по столбцу обозначает долю ушедших клиентов).

  7. Как отток клиентов зависит от числа приобретённых у банка услуг? Для ответа на этот вопрос постройте многоуровневую столбчатую диаграмму.

  8. Как влияет наличие статуса активного клиента на отток клиентов? Постройте диаграмму, иллюстрирующую это. Что бы вы предложили банку, чтобы уменьшить отток клиентов среди неактивных?

  9. В какой стране доля ушедших клиентов больше? Постройте тепловую картограмму, которая покажет это соотношение на карте мира. Предположите, с чем это может быть связано.

  10. Переведите числовой признак CreditScore в категориальный. Для этого воспользуйтесь функцией get_credit_score_cat(), которая приведена ниже. Примените её к столбцу CreditScore и создайте новый признак CreditScoreCat — категории кредитного рейтинга.

def get_credit_score_cat(credit_score):
    if credit_score >= 300 and credit_score < 500:
        return "Very_Poor"
    elif credit_score >= 500 and credit_score < 601:
        return "Poor"
    elif credit_score >= 601 and credit_score < 661:
        return "Fair"
    elif credit_score >= 661 and credit_score < 781:
        return "Good"
    elif credit_score >= 781 and credit_score < 851:
        return "Excellent"
    elif credit_score >= 851:
        return "Top"
    elif credit_score < 300:
        return "Deep"
  • Постройте сводную таблицу, строками которой являются категории кредитного рейтинга (CreditScoreCat), а столбцами — количество лет, в течение которых клиент пользуется услугами банка (Tenure). В ячейках сводной таблицы должно находиться среднее по признаку оттока (Exited) — доля ушедших пользователей.

  • На основе полученной сводной таблицы постройте тепловую карту с аннотацией. Найдите на тепловой карте категории клиентов, которые уходят чаще всего.

К содержанию

Предобработка и подготовка датасета для анализа¶

In [5]:
# убираем неиформативные признаки
churn_df = churn.data.drop(columns=['RowNumber', 'CustomerId', 'Surname'], axis=1)

# для единообразия в бинарных признаках
churn_df.rename(columns={'Exited':'isExited', 'HasCrCard':'isHasCrCard', 'IsActiveMember':'isActiveMember'}, inplace=True)

# бинарные признаки в ЧПИ
churn_df['Exited'] = churn_df['isExited'].replace({0:'No', 1:'Yes'})
churn_df['ActiveMember'] = churn_df['isActiveMember'].replace({0:'No', 1:'Yes'})
churn_df['HasCrCard'] = churn_df['isHasCrCard'].replace({0:'No', 1:'Yes'})
In [6]:
# категориальные
churn.category_columns =('Exited', 'HasCrCard', 'ActiveMember', 'Gender', 'Geography') #, 'Surname'
for column in churn.category_columns:
    churn_df[column] = pd.Categorical(churn_df[column])

# оценим
churn_df[[*churn.category_columns]].describe()
Out[6]:
Exited HasCrCard ActiveMember Gender Geography
count 10000 10000 10000 10000 10000
unique 2 2 2 2 3
top No Yes Yes Male France
freq 7963 7055 5151 5457 5014
In [7]:
# основные статистики по числовым признакам
churn_df[['Balance', 'CreditScore', 'EstimatedSalary', 'Age', 'Tenure', 'NumOfProducts']].describe().round(2)
#  много 0 в Q25.
Out[7]:
Balance CreditScore EstimatedSalary Age Tenure NumOfProducts
count 10000.00 10000.00 10000.00 10000.00 10000.00 10000.00
mean 76485.89 650.53 100090.24 38.92 5.01 1.53
std 62397.41 96.65 57510.49 10.49 2.89 0.58
min 0.00 350.00 11.58 18.00 0.00 1.00
25% 0.00 584.00 51002.11 32.00 3.00 1.00
50% 97198.54 652.00 100193.92 37.00 5.00 1.00
75% 127644.24 718.00 149388.25 44.00 7.00 2.00
max 250898.09 850.00 199992.48 92.00 10.00 4.00
In [8]:
# и в целом
churn_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 14 columns):
 #   Column           Non-Null Count  Dtype   
---  ------           --------------  -----   
 0   CreditScore      10000 non-null  int64   
 1   Geography        10000 non-null  category
 2   Gender           10000 non-null  category
 3   Age              10000 non-null  int64   
 4   Tenure           10000 non-null  int64   
 5   Balance          10000 non-null  float64 
 6   NumOfProducts    10000 non-null  int64   
 7   isHasCrCard      10000 non-null  int64   
 8   isActiveMember   10000 non-null  int64   
 9   EstimatedSalary  10000 non-null  float64 
 10  isExited         10000 non-null  int64   
 11  Exited           10000 non-null  category
 12  ActiveMember     10000 non-null  category
 13  HasCrCard        10000 non-null  category
dtypes: category(5), float64(2), int64(7)
memory usage: 752.7 KB

К содержанию

Визуальный анализ¶

1. Соотношение ушедших и лояльных клиентов.¶

Каково соотношение ушедших и лояльных клиентов? Покажите это на графике и дайте комментарий по соотношению.

In [9]:
# отток
churn.exited = churn_df.groupby('Exited')['Exited'].count().to_frame(name='Count')
churn.exited['Ratio (%)'] = round(churn.exited['Count'] / churn_df.shape[0] * 100, 2)
#print(churn.exited)
In [10]:
# в процентах
#fig=\
px.bar( data_frame=churn.exited, height=420, width=440,
    title="Соотношение ушедших и лояльных клиентов",
    x=churn.exited.index,  
    y='Ratio (%)',
    color=churn.exited.index,
    text = 'Ratio (%)',
)
#fig.show()
In [11]:
# # на круговой пожалуй не буду, ругають
# px.pie( data_frame=churn.exited.reset_index(),  height=420, width=440,
#     title="Соотношение ушедших и лояльных клиентов",
#     names=('Лояльные','Ушедшие'), #'Exited'
#     values='Ratio (%)',
#     color='Exited',
# ).show()

Вывод по общему оттоку

  • За период работы банка более 20% клиентов покинули его. Достаточная величина, что бы проанализировать отток клиетов и попробовать найти его причины.

К содержанию

2. Распределение баланса пользователей.¶

Постройте график, показывающий распределение баланса пользователей, у которых на счету больше 2 500 долларов. Опишите распределение и сделайте выводы

In [12]:
churn.balance_gt_2k5 = churn_df['Balance'] >  2_500
# ??? Уточнить это на весь анализ или только про баланс 
churn_df = churn_df[churn.balance_gt_2k5]
In [13]:
fig = px.histogram( data_frame=churn_df,  width=700, height=440,
    title='Распределение баланса пользователей(>2500$)',
    x='Balance',
    nbins=80,
    marginal='box',
    #histnorm='percent',
)
#fig.update_layout(bargap=0.05).show()
fig.update_layout(bargap=0.05)
fig.show()

Описание распределения

  • Распределение близкое к нормальному
  • Медианное значение в районе 120К. Мода - 125К
  • Первый и третий квантили 100К и 140К соответственно
  • Нижняя и верхняя граница 48K и 198К соответственно
  • Значительное количество потенциальных выбросов за нижней и верхней границей
  • Аномальные выбросы в районе близком к нулю и 250К

К содержанию

3. Распределение баланса клиента в разрезе признака оттока.¶

Посмотрите на распределение баланса клиента в разрезе признака оттока. Как различаются суммы на накопительном счёте ушедших и лояльных клиентов? Подумайте и напишите, с чем это может быть связано, что может не устраивать ушедших клиентов в банке.

In [14]:
fig = px.histogram( data_frame=churn_df , width=700, height=440,
    title='Распределение клиетов по балансу в разрезе признака оттока',
    x='Balance',
    nbins=80,
    color='Exited',
    opacity=0.5,
    marginal='box',
    barmode='overlay',
)
fig.update_layout(bargap=0.05)
fig.show()

Описание

  • Суммы на счетах ушедщих клиентов в среднем на треть меньше, чем у лояльных.
  • Распределения перекрываются
  • Нет видимых отличий, позволяющих судить об оттоке бо балансу. Но возможно банку стоит пересмотреть процент по накопительным вкладам или/и кредитный потолок и ставку по кредиту.

К содержанию

4. Распределение возраста в разрезе признака оттока.¶

Посмотрите на распределение возраста в разрезе признака оттока. В какой группе больше потенциальных выбросов? На какую возрастную категорию клиентов стоит обратить внимание банку?

In [15]:
fig = px.histogram( data_frame=churn_df , width=800, height=500,
    title='Распределение клиетов по возрасту в разрезе признака оттока',
    x='Age',
    color='Exited',
    nbins=80,
    marginal='box',
    barmode='overlay',
    opacity=0.5,
    #histnorm='percent',
)
fig.update_layout(bargap=0.05)
fig.show()

Описание

  • В группе лояльных клиентов преобладающее число потенциальных выбросов.
  • Явно виден значительный отток в старшей возрастной категории.

К содержанию

5. Взаимосвязь кредитного рейтинга клиента и его предполагаемой зарплаты.¶

Постройте график, который показывает взаимосвязь кредитного рейтинга клиента и его предполагаемой зарплаты. Добавьте расцветку по признаку оттока клиентов. Какова взаимосвязь между признаками? Если не видите явной взаимосвязи, укажите это.

In [16]:
# лайфхак - границы точек
# churn_df['_vsize'] = 1   # churn_df['isExited']+3

fig = px.scatter( data_frame=churn_df.assign(_vsize=1), # лайфхак - границы точек
    width=740, height=620, 
    title='Взаимосвязь кредитного рейтинга клиента и его предполагаемой зарплаты',
    x='CreditScore',
    y='EstimatedSalary',
    color='Exited',
    opacity=0.5,
#
    size='_vsize', # c окантовочкой глазу приятней
    hover_data={'Exited':False, '_vsize':False}, # лишнее это
    size_max=8,
#                 
    range_x=[300, 900], # зафиксируем, а то скачет
                 
)
fig.show()

Описание

  • Видимая взамосвязь между признаками кредитного рейтинга клиента и его предполагаемой зарплаты отсутствует.

К содержанию

6. Соотношение ушедших клиентов по половому признаку.¶

Кто чаще уходит, мужчины или женщины? Постройте график, который иллюстрирует это.

Процент ушедших клиентов в каждой группе можно рассчитать как среднее по столбцу Exited (так как 1 — это ушедшие клиенты, а 0 — лояльные, среднее арифметическое по столбцу обозначает долю ушедших клиентов).

In [17]:
#
churn.gender_df = churn_df.groupby(['Gender', 'Exited'], as_index=False)['isExited'].count() # , as_index=False
churn.gender_df['isExited'] = round(churn.gender_df['isExited'] / churn_df.shape[0] * 100, 2)
#display(gender_df)

fig = px.bar( churn.gender_df, width=520, height=480, 
    title='Соотношение ушедших клиентов по признаку "Пол"',
    x='Gender',
    y='isExited',
    color='Exited',
#    barmode='stack',
    barmode='group',
    opacity=0.5,
    labels={'isExited': 'Ratio (%)'},   
)
#fig.update_xaxes(type='category', categoryorder='category ascending')
fig.show()

Описание

  • Женскую половину клиентов банка явно что то серьезно не устраивает.

К содержанию

7. Соотношение оттока клиентов от числа приобретённых у банка услуг.¶

Как отток клиентов зависит от числа приобретённых у банка услуг? Для ответа на этот вопрос постройте многоуровневую столбчатую диаграмму.

In [18]:
churn.nprod_df = churn_df.groupby(['Exited','NumOfProducts'], as_index=False)['isExited'].count() # , as_index=False
churn.nprod_df['isExited'] = round(churn.nprod_df['isExited'] / churn_df.shape[0] * 100, 2)
#display(nprod_df)

fig = px.bar( churn.nprod_df, width=520, height=480, 
    title='Соотношение оттока от числа услуг',
    x='NumOfProducts',
    y='isExited',
    color='Exited',
#    barmode='stack',
    barmode='group',
    opacity=0.5,
    labels={'isExited': 'Ratio (%)'},   
)
#fig.update_xaxes(type='category', categoryorder='category ascending')
fig.show()

Описание

  • Судя по осутсвию доп.услуг у лояльных клиентов, это что то связанное с прекращением обслуживания и закрытием счетов. И как постфактум не связан с причиной оттока.

К содержанию

8. Влияние наличия статуса активного клиента на отток клиентов.¶

Как влияет наличие статуса активного клиента на отток клиентов? Постройте диаграмму, иллюстрирующую это. Что бы вы предложили банку, чтобы уменьшить отток клиентов среди неактивных?

In [19]:
churn.active_df = churn_df.groupby(['Exited','ActiveMember'], as_index=False)['isExited'].count() # , as_index=False
#display(active_df)
churn.active_df['isExited'] = round(churn.active_df['isExited'] / churn_df.shape[0] * 100, 2)
#display(active_df)

fig = px.bar( churn.active_df, width=520, height=480, 
    title='Соотношение оттока клиентов от активности',
    x='ActiveMember',
    y='isExited',
    color='Exited',
#    barmode='stack',
    barmode='group',
    opacity=0.5,
    labels={'isExited': 'Ratio (%)'},   
)
#fig.update_xaxes(type='category', categoryorder='category ascending')
fig.show()

Описание

  • Среди активных(?) клиентов доля ушедших значительно ниже. Возможно увеличение партнеров по потребительскому кредитованию и привлечение клиентов к инвестиционным проектам изменит эту ситуацию к лучшему.

К содержанию

9. Распределение ушедших клиентов по странам. Тепловая картограмма.¶

В какой стране доля ушедших клиентов больше? Постройте тепловую картограмму, которая покажет это соотношение на карте мира. Предположите, с чем это может быть связано.

In [20]:
churn.country = churn_df.groupby('Geography')['isExited'].mean().mul(100).round(2)
churn.country_df = churn.country.to_frame(name='Count').reset_index()
#display(churn.country_df)

fig = px.choropleth(  data_frame=churn.country_df,
    width=600, height=480, 
    title='Тепловая карта оттока клиентов по странам в %',
    locations="Geography",
    locationmode = "country names",
    color="Count",
    color_continuous_scale='OrRd', # 'Reds' 
    range_color=[0, churn.country_df['Count'].max()],
    scope="europe",
    labels={'Count':'Ratio (%)'},
)

fig.update_geos(fitbounds="locations", visible = False, showcountries=True, )
#fig.update_layout(margin={"r":0,"t":80,"l":0,"b":0})
#fig.update_layout(showlegend=True,)
fig.show()

Описание

  • Германия лидирует по доле оттока (более 30%) на фоне Франции и Испании.
  • Возможно именно на Германию приходиться больший долевой отток в старшей возростной группе.
In [21]:
fig = px.histogram(
    data_frame=churn_df[churn_df['Geography']=='Germany'] ,
    width=800, height=520,
    title='Распределение клиетов в Германии по возрасту в разрезе признака оттока',
    x='Age',
    color='Exited',
    nbins=60,
    marginal='box',
    barmode='overlay',
    opacity=0.5,
    #histnorm='percent',
    category_orders={'Exited':['No','Yes']}
)
fig.update_layout(bargap=0.05)
fig.show()
In [22]:
fig = px.histogram(
    data_frame=churn_df[churn_df['Geography'].isin(['France','Spain'])] ,
    width=800, height=520,
    title='Распределение во Франции и Испании по возрасту в разрезе признака оттока',
    x='Age',
    color='Exited',
    nbins=60,
    marginal='box',
    barmode='overlay',
    opacity=0.5,
    category_orders={'Exited':['No','Yes']}
)
fig.update_layout(bargap=0.05)
fig.show()

Примечание

  • Как видно, в немецком отделении банка значительное преобладание оттока клиентов в старшей возростной группе по сравнению с французским и испанским.

К содержанию

10. Работа с категорией кредитного рейтинга. Тепловая карта по оттоку.¶

Переведите числовой признак CreditScore в категориальный. Для этого воспользуйтесь функцией get_credit_score_cat(). Примените её к столбцу CreditScore и создайте новый признак CreditScoreCat — категории кредитного рейтинга.

  • Постройте сводную таблицу, строками которой являются категории кредитного рейтинга (CreditScoreCat), а столбцами — количество лет, в течение которых клиент пользуется услугами банка (Tenure). В ячейках сводной таблицы должно находиться среднее по признаку оттока (Exited) — доля ушедших пользователей.

  • На основе полученной сводной таблицы постройте тепловую карту с аннотацией. Найдите на тепловой карте категории клиентов, которые уходят чаще всего.

In [23]:
def get_credit_score_cat(credit_score):
    if credit_score >= 300 and credit_score < 500:
        return "Very_Poor"
    elif credit_score >= 500 and credit_score < 601:
        return "Poor"
    elif credit_score >= 601 and credit_score < 661:
        return "Fair"
    elif credit_score >= 661 and credit_score < 781:
        return "Good"
    elif credit_score >= 781 and credit_score < 851:
        return "Excellent"
    elif credit_score >= 851:
        return "Top"
    elif credit_score < 300:
        return "Deep"
In [24]:
#CreditScoreCat
churn_df['CreditScoreCat'] = churn_df['CreditScore'].apply(get_credit_score_cat)

churn_df['CreditScoreCat'] = pd.Categorical(churn_df['CreditScoreCat'], ordered=True, 
    categories=['Deep', 'Very_Poor', 'Poor', 'Fair', 'Good', 'Excellent', 'Top']
)
#churn_df['CreditScoreCat']
In [25]:
churn_df['Tenure Years'] = churn_df['Tenure'] + 1 

churn.age_pivot = churn_df.pivot_table(
    columns='Tenure Years',
    index='CreditScoreCat',
    values='isExited',
    aggfunc=np.mean
).mul(100).round(2)
#churn.age_pivot
In [26]:
fig = px.imshow(churn.age_pivot,
    width=860, height=480, 
    title="Тепловая карта оттока клиентов по категории кредитного\n рейтинга и годам обслуживания (%)",
    text_auto='.0f',
    color_continuous_scale='Blues', # "Viridis"
)
fig.show()

Описание

  • Набольший отток(30% и более) пребладает в категории Very_Poor (1, 5, 9, 10 и 11 год обслуживания) и Excellent (1, 6(близко) 7, 10 и 11 год обслуживания).

К содержанию

Приложение¶

In [27]:
# версионинг. сверим часы ;)
import sys
import platform
import plotly

def version_control(you_only=False):
    module_name = ['OS', 'Python', 'Numpy', 'Pandas', 'Plotly']
    module_main = ['Windows-7 SP1', '3.7.9', '1.21.6', '1.3.5', '5.8.2']
    module_version = [
        platform.platform(),
        sys.version.split()[0] ,   # +' x'+sys.version.split()[8]
        np.__version__,
        pd.__version__,
        plotly.__version__,
    ]
    if you_only:
        return pd.Series(module_version, module_name).to_frame(name='Version')
    else:
        return pd.DataFrame({'You Version':module_version, 'My Version': module_main, }, index=module_name)
    
print('Module version control\n',version_control())
Module version control
                    You Version     My Version
OS      Windows-7-6.1.7601-SP1  Windows-7 SP1
Python                   3.7.9          3.7.9
Numpy                   1.21.6         1.21.6
Pandas                   1.3.5          1.3.5
Plotly                   5.8.2          5.8.2

SF DST-148 Дмитрий Орлов Январь 2023

К содержанию